/* Maxim DS3234 driver implementation */

/* Copyright (C) 2011-2012 David Zanetti
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; version 2 of the License.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

/* This driver covers both the register access to the chip over SPI, and run local timers from the 32.768kHz output */
/* This effective implements the same features as the rtc driver but with the 2ppm DS3234 instead of a 20ppm crystal */

#include <avr/io.h>
#include <avr/pgmspace.h>
#include <stdio.h>
#include <string.h>
#include <avr/interrupt.h>
#include <util/atomic.h>

#include "global.h"
#include <util/delay.h>
#include <stdlib.h>

#include "serial.h"

#include "ds3234.h"
#include "gps.h"
#include "spi.h"

#define DS3234_CS_PORT PORTF
#define DS3234_CS_PIN PIN2_bm

#define SPI_START DS3234_CS_PORT.OUTCLR = DS3234_CS_PIN
#define SPI_END DS3234_CS_PORT.OUTSET = DS3234_CS_PIN

/* DS3234 registers */
#define DS3234_SECONDS 0x00
#define DS3234_MINUTES 0x01
#define DS3234_HOURS 0x02
#define DS3234_DAY 0x03
#define DS3234_DATE 0x04
#define DS3234_MON_CEN 0x05
#define DS3234_YEAR 0x06
#define DS3234_A1_SECONDS 0x07
#define DS3234_A1_MINUTES 0x08
#define DS3234_A1_HOURS 0x09
#define DS3234_A1_DAY_DATE 0x0a
#define DS3234_A2_MINUTES 0x0b
#define DS3234_A2_HOURS 0x0c
#define DS3234_A2_DAY_DATE 0x0d
#define DS3234_CONTROL 0x0e
#define DS3234_CONTROL_STATUS 0x0f
#define DS3234_AGING 0x10
#define DS3234_TEMP_MSB 0x11
#define DS3234_TEMP_LSB 0x12
#define DS3234_TEMP_CONV 0x13
#define DS3234_SRAM_ADDR 0x18
#define DS3234_SRAM_DATA 0x19
/* add this to perform register write */
#define DS3234_WRITE 0x80
/* status and control bits */
#define DS3234_12_24 0x40
#define DS3234_AM_PM 0x20
#define DS3234_DY_DT 0x80
#define DS3234_A1M1 0x80
#define DS3234_A1M2 0x80
#define DS3234_A1M3 0x80
#define DS3234_A1M4 0x80
#define DS3234_A2M2 0x80
#define DS3234_A2M3 0x80
#define DS3234_A2M4 0x80
#define DS3234_EOSC 0x80
#define DS3234_BBSQW 0x40
#define DS3234_CONV 0x20
#define DS3234_RS2 0x10
#define DS3234_RS1 0x08
#define DS3234_INTCN 0x04
#define DS3234_A2IE 0x02
#define DS3234_A1IE 0x01
#define DS3234_OSF 0x80
#define DS3234_BB32KHZ 0x40
#define DS3234_CRATE1 0x20
#define DS3234_CRATE0 0x10
#define DS3234_EN32KHZ 0x08
#define DS3234_BSY 0x04
#define DS3234_A2F 0x02
#define DS3234_A1F 0x01
#define DS3234_BB_TD 0x01

/* interrupt to handle overflow of the timers into normal variables */
ISR(TCE0_OVF_vect) {
	ds3234_msb_s++;
	if (!ds3234_msb_s) {
		ds3234_era++;
	}
}

/* init the timers and chip */
void ds3234_init(void) {

	/* set up SPI access to the chip */
	DS3234_CS_PORT.DIRSET = DS3234_CS_PIN;
	SPI_END;

	/* reset MSB portion and era value */
	ds3234_msb_s = 0;
	ds3234_era = 0;

	/* - DS3234 32.768kHz output feeds a pin on the MCU.
	 * - We pipe that into the event system to increment a timer (0).
	 * - When timer 0 overflows, increment timer 1 via the event system.
	 * - When timer 1 overflows, fire interrupt to increment a global.
	 * - When ds3234_msb_s overflows, increment the era value
	 * - Both timers perform an input capture on another event channel (sourced from PPS GPS line)
	 *
	 * |           40 bits of seconds         | | 15 bits of fractions |
	 * |39|....|32|31|......|16|15|........|00|.|00|01|..........|13|14|
	 * |ds3234_era|ds3234_msb_s|   TCE0       | |         TCD0         |
	 *
	 */

	/* power up TCD0 and TCE0 */
	PR.PRPD &= ~(PR_TC0_bm);
	PR.PRPE &= ~(PR_TC0_bm);

	/* configure given port to have a pull-up, and positive edge detection */
	PORTF.DIRCLR = PIN1_bm; /* input */
	PORTF.PIN1CTRL = PORT_ISC_RISING_gc; /* rising edge only. avr1001 is wrong on this */
	//PORTC.PIN0CTRL = PORT_ISC_RISING_gc | PORT_OPC_PULLUP_gc; /* rising edge */

	/* timer reset */
	TCD0.CNT = 0;
	TCE0.CNT = 0;

	/* interrupt to deal with TCD0 overflow */
	TCE0.INTCTRLA |= TC_OVFINTLVL_MED_gc; /* medium level interrupt */

	/* enable medium level interrupts, global still might be off */
	PMIC.CTRL |= PMIC_MEDLVLEX_bm;

	/* timerc1 is fed from timerc0 overflows */
	EVSYS.CH2MUX = EVSYS_CHMUX_TCD0_OVF_gc; /* tcc0 ovf -> event channel 2 */
	TCE0.CTRLD = TC_EVSEL_CH0_gc | TC_EVACT_CAPT_gc | TC1_EVDLY_bm; /* listen for ch0 event, do input capture, 1 clock delay on capture triggering */
	TCE0.CTRLB |= TC1_CCAEN_bm; /* enable input capture A */
	TCE0.CTRLA = TC_CLKSEL_EVCH2_gc; /* clock us from events on channel 2 */
	/* timerc0 is fed from DS3234 clock input pin being triggered (high, because it's rising edge above */
	EVSYS.CH1MUX = EVSYS_CHMUX_PORTF_PIN1_gc; /* portf1 -> event channel 1 */
	TCD0.CTRLD = TC_EVACT_CAPT_gc | TC_EVSEL_CH0_gc; /* listen for ch0 event, do input capture */
	TCD0.CTRLB |= TC0_CCAEN_bm; /* enable input capture A */
	TCD0.PER = 32767; /* roll over at the top of 1s of ticks */
	TCD0.CTRLA = TC_CLKSEL_EVCH1_gc; /* clock us from events on channel 1 */


	return;
}

/* reset the clock to the given value */
void ds3234_hard_step(uint32_t s) {

	/* suspend the clock */
	/* FIXME: this needs to be less brutal about it */

	TCD0.CTRLA = 0; /* disable clocking from event pin */
	TCE0.INTCTRLA = 0; /* disable interrupts on ovf of lsb timer */
	_nop(); _nop(); /* instruction delay to handle event propogation */

	/* reset the clock */
	ds3234_msb_s = (s >> 16);
	TCE0.CNT = (s & 0xffff);
	TCD0.CNT = 0;

	/* restart the clock */
	TCE0.INTCTRLA |= TC_OVFINTLVL_MED_gc; /* low level interrupt */
	TCD0.CTRLA = TC_CLKSEL_EVCH1_gc; /* clock from events on channel 1 */

	return;
}

void ds3234_printtime(void) {
	serial0_tx_dec(((uint32_t) ds3234_msb_s << 16) | (uint32_t) TCE0.CNT);
}

void ds3234_print_temp(void) {
	uint8_t temp, temp_lsb;

	/* query the temp register on the chip */

	/* chip only accepts 2-4MHz max, and wants Mode1 or Mode3 */
	spi_slow_mode1();

	/* MSB first */
	SPI_START;
	spi_txrx(DS3234_TEMP_MSB);
	temp = spi_txrx(0xff);
	SPI_END;

	/* now LSB */
	SPI_START;
	spi_txrx(DS3234_TEMP_LSB);
	temp_lsb = spi_txrx(0xff);
	SPI_END;

	/* MSB has a sign bit at the top */
	if (temp & 0x80) {
		serial0_tx_cout('-');
	}
	/* then the rest of the MSB is just as-is */
	serial0_tx_dec(temp & 0x7f);
	serial0_tx_cout('.');
	/* fraction is the upper 2 bits, covering 1/4 degrees */
	serial0_tx_dec((temp_lsb >> 6) * 25);

	return;
}

void ds3234_align(void) {
	uint32_t offset;
	uint16_t timer_ms; /* not really milliseconds, timer captured value */
	uint32_t timer_s; /* current time */

	/* save off the timer's capture, discarding sense bit */
	timer_ms = TCD0.CCA & 0x7fff;
	/* and the seconds combined with MSB */
	timer_s = ((uint32_t) ds3234_msb_s_pps << 16) | (uint32_t) TCE0.CCA;

#ifdef DEBUG_DS3234
	/* report current values */
	serial0_tx_PGM(PSTR("ds3234: "));
	//serial0_tx_dec(ds3234_msb_s);
	//serial0_tx_cout(' ');
	serial0_tx_dec(((uint32_t) ds3234_msb_s_pps << 16) | (uint32_t) timer_s);
	serial0_tx_cout(' ');
	serial0_tx_dec(timer_ms);
	serial0_tx_cout(' ');
	serial0_tx_dec(gps_s);
	serial0_tx_cr();

#endif

	/* compare the stored PPS time against the GPS time, and work out our difference */
	/* seconds difference, the partial seconds we'll infer by other means */
	/* -1 is there so we align to just behind it */

	/* if we are ahead of GPS, reset to several seconds back */
	if (gps_s <= timer_s) {
		serial0_tx_PGM(PSTR("ds3234: overshoot, hard step back\r\n"));
		clear_flag(flag_clock,FLAG_CLOCK_SLEW);
		clear_flag(flag_clock,FLAG_CLOCK_SYNC);
		ds3234_hard_step(gps_s); /* about 0.1-0.9 seconds behind */
		return;
	}

	offset = gps_s - timer_s;

	if (offset > 2) {
		/* more than 2 seconds away, just hard reset it */
		serial0_tx_PGM(PSTR("ds3234: hard step forward\r\n"));
		clear_flag(flag_clock,FLAG_CLOCK_SLEW);
		clear_flag(flag_clock,FLAG_CLOCK_SYNC);
		ds3234_hard_step(gps_s); /* about 0.1-0.9 seconds behind */
		return;
	}

	/* at this point we're less than 2 seconds away, so multiply out the number of ticks */
	offset = (offset * 32768) - timer_ms;

#ifdef DEBUG_DS3234
	serial0_tx_PGM(PSTR("ds3234: need to add "));
	serial0_tx_dec(offset);
	serial0_tx_PGM(PSTR(" ticks\r\n"));
#endif

	if (offset > 400) {
#ifdef DEBUG_DS3234
		serial0_tx_PGM(PSTR("ds3234: inserting 200 ticks\r\n"));
#endif
		set_flag(flag_clock,FLAG_CLOCK_SLEW);
		clear_flag(flag_clock,FLAG_CLOCK_SYNC);
		uint8_t n;
		for (n = 0; n < 200; n++) {
			/* BUG: EVSYS.STROBE is in the wrong place in the avr-libc headers, use the direct name */
			EVSYS_STROBE = 0x2; /* Channel 1 */
		}
		return;
	}

	if (offset > 20) {
		set_flag(flag_clock,FLAG_CLOCK_SLEW);
		clear_flag(flag_clock,FLAG_CLOCK_SYNC);
		EVSYS_STROBE = 0x2;
                EVSYS_STROBE = 0x2;
                EVSYS_STROBE = 0x2;
                EVSYS_STROBE = 0x2;
                EVSYS_STROBE = 0x2;
		return;
	}

	if (offset > 3) {
#ifdef DEBUG_DS3234
		serial0_tx_PGM(PSTR("ds3234: inserting a tick\r\n"));
#endif
		EVSYS_STROBE = 0x2;
		set_flag(flag_clock,FLAG_CLOCK_SLEW);
		return;
	}

	if (offset < 2) {
#ifdef DEBUG_DS3234
		serial0_tx_PGM(PSTR("ds3234: supressing a tick\r\n"));
#endif
		TCD0.CTRLA = 0; /* disable clocking from event pin */
		_delay_us(35); /* should cover one tick, maybe two */
		TCD0.CTRLA = TC_CLKSEL_EVCH1_gc; /* clock from events on channel 1 again */
		set_flag(flag_clock,FLAG_CLOCK_SLEW); /* just so we get a confirmed sync again */
		return;
	}

	if (flag_clock & FLAG_CLOCK_SLEW) {
		serial0_tx_PGM(PSTR("ds3234: synced\r\n"));
		clear_flag(flag_clock,FLAG_CLOCK_SLEW);
		set_flag(flag_clock,FLAG_CLOCK_SYNC);
#if 0
		/* work out ppm */
		if (ds3234_reference_s) {
		 double ds3234_ppm;
		 char str[64] = "";

		 ds3234_ppm = (1.0 / (gps_s - ds3234_reference_s) * 32768) / 1000000.0;
		 snprintf(str,63,"ds3234: %.3f ppm\r\n",ds3234_ppm);
		 serial0_tx(str,strlen(str));
		}
#endif
//		/* set reference time, with built-in fudge */
//		ds3234_reference_s = ((uint32_t)ds3234_msb_s << 16) | ((uint32_t)TCE0.CNT);
//		ds3234_reference_ms = TCD0.CNT & 0x7fff;
//#ifdef GPS_FUDGE
//		ds3234_reference_ms -= GPS_FUDGE; /* ugly hack! */
//		if (ds3234_reference_ms > 0x7fff) {
//		 ds3234_reference_s--;
//		 ds3234_reference_ms = ds3234_reference_ms & 0x7fff;
//		}
//#endif
		ds3234_now_ntp(&ds3234_reference_s,&ds3234_reference_ms);
		//ds3234_reference_rand = rand();
	}
	return;
}

/* return the current date time in ntp base, with correction for leap seconds, including GPS fudge */
void ds3234_now_ntp(uint32_t *seconds, uint16_t *fraction) {
	/* not sure what the 20 seconds is doing here, looks like TAI but shouldn't be needed? */
	(*seconds) = (((uint32_t)ds3234_msb_s << 16) | ((uint32_t)TCE0.CNT)) - gps_leap - 20 + 2208988800UL + 315964819UL;
	(*fraction) = TCD0.CNT & 0x7fff;
#ifdef GPS_FUDGE
	(*fraction) -= GPS_FUDGE; /* ugly hack! */
		if ((*fraction) > 0x7fff) {
		 (*seconds)--;
		 (*fraction) = (*fraction) & 0x7fff;
		}
#endif
}
